<template>
  <div id='container'>
    <div class='panel'></div>
  </div>
</template>

<script lang='ts'>
import { Component, Prop, Vue } from 'vue-property-decorator';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
import Stats from 'three/examples/jsm/libs/stats.module.js';
// @ts-ignore
import { GUI } from 'three/examples/jsm/libs/dat.gui.module.js';

@Component({
  components: {}
})
export default class extends Vue {
  renderer: any;
  scene: any;
  camera: any;
  controls: any;
  container: any;
  grid: any;
  stats: any;
  mouse: any;
  raycaster: any;
  selectObject: any;
  gui: any;
  mesh: any;

  mounted() {
    this.init();
    this.animate();
    document.addEventListener('click', this.onMouseClick, false);
  }

  init() {
    this.initScene();
    this.initCamera();
    this.initRenderer();
    // this.initGrid();
    this.initContent();
    this.initLight();
    this.initControls();
    this.initStats();
    this.initRayCaster();
    this.initGui();
  }

  initScene() {
    this.scene = new THREE.Scene();
    this.container = document.getElementById('container');
  }

  initRenderer() {
    this.renderer = new THREE.WebGLRenderer({
      antialias: true
    });
    this.renderer.setSize(
      this.container.clientWidth,
      this.container.clientHeight
    );
    this.renderer.setClearColor(0x000000, 1.0);
    this.container && this.container.appendChild(this.renderer.domElement);
  }

  initCamera() {
    this.camera = new THREE.PerspectiveCamera(
      70,
      this.container.clientWidth / this.container.clientHeight,
      0.01,
      1000
    );
    this.camera.position.set(1, 5, 1);
    this.camera.layers.toggle(1);
    this.camera.layers.toggle(2);
    this.camera.layers.toggle(3);
    this.camera.layers.toggle(4);
    this.camera.layers.toggle(5);
    this.camera.layers.toggle(6);
  }

  initControls() {
    this.controls = new OrbitControls(this.camera, this.renderer.domElement);
    // this.controls.enableDamping = true;
    // this.controls.dampingFactor = 1;
    // // 视角最小距离
    // this.controls.minDistance = 0.01;
    // // 视角最远距离
    // this.controls.maxDistance = 100;
    // 最大角度
    // this.controls.maxPolarAngle = Math.PI / 2.2;
    // this.controls.target = new THREE.Vector3(0, 0, 0);
  }

  initContent() {
    // this.createFloor();
    this.cretateContent();
    // this.createTest();
  }

  initGrid() {
    this.grid = new THREE.GridHelper(50, 50);
    this.grid.position.set(0, 0, -5);
    this.scene.add(this.grid);
  }

  // 创建地板
  createFloor() {
    var loader = new THREE.TextureLoader();
    loader.load('/static/floor.jpg', texture => {
      texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
      texture.repeat.set(20, 20);
      var floorGeometry = new THREE.BoxGeometry(100, 100, 1);
      var floorMaterial = new THREE.MeshBasicMaterial({
        map: texture
      });
      var floor = new THREE.Mesh(floorGeometry, floorMaterial);
      floor.rotation.x = -Math.PI / 2;
      floor.position.x = 35;
      floor.position.z = -15;
      floor.name = 'floor';
      this.scene.add(floor);
    });
  }

  // 创建货架和库位
  cretateContent() {
    let loader = new GLTFLoader();
    loader.load('/static/huojia33.glb', (gltf: any) => {
      // this.mergeAddToSceneTest(gltf);
      // this.addToScene(gltf);
      this.mergeAddToScene(gltf);
    });
  }

  createTest() {
    let loader = new THREE.BufferGeometryLoader();
    loader.load('/static/test.json', (geometry: any) => {
      // this.addToSceneJson(geometry);
      this.test();
    });
  }

  test() {
    let COUNT = 50000;
    const instanceEmissiveArray = new Uint8Array(COUNT * 3)
    const emissiveAttribute = new THREE.InstancedBufferAttribute(instanceEmissiveArray, 3, true)
    var geometry = new THREE.BoxBufferGeometry(0.8, 0.8, 0.8);
    // ------------------------------------------------------------------
    var instancedGeometry = new THREE.InstancedBufferGeometry(); // this is going to wrap both geometry and a bit of the scene graph

    // we have to copy the meat - geometry into this wrapper
    Object.keys(geometry.attributes).forEach(attributeName => {
      instancedGeometry.attributes[attributeName] =
        geometry.attributes[attributeName];
    });

    // along with the index
    instancedGeometry.index = geometry.index;

    // we define the size of the buffer
    instancedGeometry.maxInstancedCount = COUNT;

    // store this in a structure for easier access
    const matArraySize = COUNT * 4;
    let matrixArray = [
      new Float32Array(matArraySize),
      new Float32Array(matArraySize),
      new Float32Array(matArraySize),
      new Float32Array(matArraySize)
    ];
    let matrixAttributeArray = []; // same for later access

    // add attributes to geometry
    for (let i = 0; i < matrixArray.length; i++) {
      const attribute = new THREE.InstancedBufferAttribute(matrixArray[i], 4);
      matrixAttributeArray.push(attribute);
      instancedGeometry.addAttribute(`aInstanceMatrix${i}`, attribute);
    }

    let instanceColorArray = new Uint8Array(COUNT * 3);
    let instanceColorAttribute = new THREE.InstancedBufferAttribute(
      instanceColorArray,
      3,
      true
    );
    instancedGeometry.addAttribute('aInstanceColor', instanceColorAttribute);

    instancedGeometry.addAttribute('aInstanceEmissive', emissiveAttribute);

    // populate entire set
    for (var i = 0; i < COUNT; i++) {
      var object = new THREE.Mesh(geometry);

      object.userData.index = i; // keep for mapping to attribute

      const color = new THREE.Color(Math.random() * 0xffffff);

      // // ------------------------------------------------------------------
      // object.position.x = Math.random() * 800 - 400;
      // object.position.y = Math.random() * 800 - 400;
      // object.position.z = Math.random() * 800 - 400;

      // object.rotation.x = Math.random() * 2 * Math.PI;
      // object.rotation.y = Math.random() * 2 * Math.PI;
      // object.rotation.z = Math.random() * 2 * Math.PI;

      // object.scale.x = Math.random() + 0.5;
      // object.scale.y = Math.random() + 0.5;
      // object.scale.z = Math.random() + 0.5;

      // // ------------------------------------------------------------------

      let huojianum = COUNT / 25;
      let col = huojianum / 80;

      let currentShelf = Math.floor(i / 25);
      let currentShelfNum = i % 25;

      object.position.x = ((currentShelf / 80) * 7) + (currentShelfNum / 5);
      object.position.y = currentShelfNum % 5;
      object.position.z = (currentShelf % 80) * 2;
      object.updateMatrixWorld();

      // refactor this
      for (let r = 0; r < 4; r++) {
        for (let c = 0; c < 4; c++) {
          matrixArray[r][i * 4 + c] = object.matrixWorld.elements[r * 4 + c]; // fetch matrix value
        }
      }

      const colorArray = color.toArray().map(c => Math.floor(c * 255)); // remap color once
      object.userData.color = colorArray; // store on graph

      for (let c = 0; c < 3; c++) {
        instanceColorArray[i * 3 + c] = colorArray[c];
      }
    }

    const instanceMaterial = new THREE.MeshLambertMaterial();

    // gnarly shader modification:
    instanceMaterial.onBeforeCompile = shader => {
      shader.vertexShader = `
        attribute vec4 aInstanceMatrix0;
        attribute vec4 aInstanceMatrix1;
        attribute vec4 aInstanceMatrix2;
        attribute vec4 aInstanceMatrix3;

        attribute vec3 aInstanceColor;
        attribute vec3 aInstanceEmissive;

        ${shader.vertexShader.replace(
    '#include <begin_vertex>',
    `
            mat4 aInstanceMatrix = mat4(
              aInstanceMatrix0,
              aInstanceMatrix1,
              aInstanceMatrix2,
              aInstanceMatrix3
            );

            vec3 transformed = (aInstanceMatrix * vec4( position , 1. )).xyz;
            `
  )}
      `;

      shader.vertexShader = `
        varying vec3 vInstanceColor;
        varying vec3 vInstanceEmissive;
        ${shader.vertexShader.replace(
    `#include <color_vertex>`,
    `#include <color_vertex>
            vInstanceColor = aInstanceColor;
            vInstanceEmissive = aInstanceEmissive;
          `
  )}
      `;

      shader.fragmentShader = `
        varying vec3 vInstanceColor;
        ${shader.fragmentShader.replace(
    'vec4 diffuseColor = vec4( diffuse, opacity );',
    'vec4 diffuseColor = vec4( vInstanceColor, opacity );'
  )}
        `;

      shader.fragmentShader = `
        varying vec3 vInstanceEmissive;
        ${shader.fragmentShader.replace(
    'vec3 totalEmissiveRadiance = emissive;',
    'vec3 totalEmissiveRadiance = vInstanceEmissive;'
  )}
      `;

      shader.vertexShader = shader.vertexShader.replace(
        `#include <beginnormal_vertex>`,
        `
        mat4 _aInstanceMatrix = mat4(
        aInstanceMatrix0,
        aInstanceMatrix1,
        aInstanceMatrix2,
        aInstanceMatrix3
        );
        vec3 objectNormal = (_aInstanceMatrix * vec4( normal, 0. ) ).xyz;
        `
      );
    };

    // we have to add another node (Mesh), because thats how the API works,
    this.scene.add(new THREE.Mesh(instancedGeometry, instanceMaterial));
  }

  addToSceneJson(geometry: any) {
    console.log(geometry);
    let count = 5000;
    var instanceColors = [];
    for (let i = 0; i < count; i++) {
      instanceColors.push(Math.random());
      instanceColors.push(Math.random());
      instanceColors.push(Math.random());
    }

    geometry.setAttribute(
      'instanceColor',
      new THREE.InstancedBufferAttribute(new Float32Array(instanceColors), 3)
    );
    geometry.computeVertexNormals();
    geometry.computeVertexNormals();
    let material = new THREE.MeshNormalMaterial();
    var matrix = new THREE.Matrix4();
    var mesh = new THREE.InstancedMesh(geometry, material, 5000);

    var colorParsChunk = [
      'attribute vec3 instanceColor;',
      'varying vec3 vInstanceColor;',
      '#include <common>'
    ].join('\n');

    var instanceColorChunk = [
      '#include <begin_vertex>',
      '\tvInstanceColor = instanceColor;'
    ].join('\n');

    var fragmentParsChunk = [
      'varying vec3 vInstanceColor;',
      '#include <common>'
    ].join('\n');

    var colorChunk = [
      'vec4 diffuseColor = vec4( diffuse * vInstanceColor, opacity );'
    ].join('\n');

    material.onBeforeCompile = function(shader) {
      shader.vertexShader = shader.vertexShader
        .replace('#include <common>', colorParsChunk)
        .replace('#include <begin_vertex>', instanceColorChunk);

      shader.fragmentShader = shader.fragmentShader
        .replace('#include <common>', fragmentParsChunk)
        .replace('vec4 diffuseColor = vec4( diffuse, opacity );', colorChunk);

      console.log(shader.uniforms);
      console.log(shader.vertexShader);
      console.log(shader.fragmentShader);
    };

    for (let i = 0; i < 5000; i++) {
      this.randomizeMatrix()(matrix);
      mesh.setMatrixAt(i, matrix);
    }

    this.scene.add(mesh);
  }

  randomizeMatrix() {
    var position = new THREE.Vector3();
    var rotation = new THREE.Euler();
    var quaternion = new THREE.Quaternion();
    var scale = new THREE.Vector3();

    return function(matrix: any) {
      position.x = Math.random() * 40 - 20;
      position.y = Math.random() * 40 - 20;
      position.z = Math.random() * 40 - 20;

      rotation.x = Math.random() * 2 * Math.PI;
      rotation.y = Math.random() * 2 * Math.PI;
      rotation.z = Math.random() * 2 * Math.PI;

      quaternion.setFromEuler(rotation);

      scale.x = scale.y = scale.z = Math.random() * 1;

      matrix.compose(position, quaternion, scale);
    };
  }

  // gltf添加到场景--test
  mergeAddToSceneTest(gltf: any) {
    console.log(gltf.scene);
    var geometry = new THREE.BoxBufferGeometry(0.8, 0.8, 0.8);

    let materials = [
      new THREE.MeshLambertMaterial({ color: 0xff0000 }),
      new THREE.MeshLambertMaterial({ color: 0.2 * 0xff0000 }),
      new THREE.MeshLambertMaterial({ color: 0.4 * 0xff0000 }),
      new THREE.MeshLambertMaterial({ color: 0.6 * 0xff0000 }),
      new THREE.MeshLambertMaterial({ color: 0.8 * 0xff0000 })
    ];
    let mergeGeometry: Array<any> = [];
    let material = gltf.scene.children[0].material;
    gltf.scene.children[0].layers.set(6);
    let clone = gltf.scene.children[0].clone();
    var matrix = new THREE.Matrix4();
    var mesh = new THREE.InstancedMesh(clone.geometry, clone.material, 5000);
    let cloneGeo = new THREE.Geometry().fromBufferGeometry(clone.geometry);
    for (let m = 1; m <= 20; m++) {
      for (let i = 1; i <= 5; i++) {
        for (let j = 1; j <= 5; j++) {
          clone.position.x = i + (m / 20) * 7;
          clone.position.y = j;
          clone.position.z = (m % 20) * 3 - 50;
          clone.name = 'huojia';
          clone.updateMatrix();

          var position = new THREE.Vector3();
          var rotation = new THREE.Euler();
          var quaternion = new THREE.Quaternion();
          var scale = new THREE.Vector3();
          position.x = clone.position.x
          position.y = clone.position.y
          position.z = clone.position.z
          matrix.compose(position, quaternion, scale);
          mesh.setMatrixAt(i * m * j - 1, matrix);
          console.log(clone.matrix);

          // let cloneGeo = new THREE.Geometry().fromBufferGeometry(
          //   clone.geometry
          // );
          // clone.updateMatrix();

          // if (!mergeGeometry[j]) {
          //   mergeGeometry[j] = new THREE.Geometry();
          // }
          // mergeGeometry[j].merge(cloneGeo, clone.matrix, 0);

          // let index = Math.floor(Math.random() * 5);
          // let object = new THREE.Mesh(geometry, materials[index]);

          let object = new THREE.Mesh(
            geometry,
            new THREE.MeshLambertMaterial({ color: Math.random() * 0xffffff })
          );

          object.name = 'huowu';
          object.position.x = clone.position.x;
          object.position.y = clone.position.y + 0.4;
          object.position.z = clone.position.z;
          object.userData = {
            id: Math.random(),
            skuName: '花生豆'
          };
          object.layers.set(j);
          this.scene.add(object);
        }
      }
    }
    this.scene.add(mesh);
    // mergeGeometry.forEach((item: any, index) => {
    //   let mesh = new THREE.Mesh(item, material);
    //   mesh.layers.set(6);
    //   this.scene.add(mesh);
    // });
  }

  // gltf添加到场景
  addToScene(gltf: any) {
    console.log(gltf.scene);
    let geometry = new THREE.BoxBufferGeometry(0.8, 0.8, 0.8);
    let materials = [
      new THREE.MeshLambertMaterial({ color: 0xff0000 }),
      new THREE.MeshLambertMaterial({ color: 0.2 * 0xff0000 }),
      new THREE.MeshLambertMaterial({ color: 0.4 * 0xff0000 }),
      new THREE.MeshLambertMaterial({ color: 0.6 * 0xff0000 }),
      new THREE.MeshLambertMaterial({ color: 0.8 * 0xff0000 })
    ];
    for (let m = 0; m < 200; m++) {
      for (let i = 0; i < 5; i++) {
        for (let j = 0; j < 5; j++) {
          let clone = gltf.scene.children[0].clone();
          clone.position.x = i + (m / 20) * 7;
          clone.position.y = j + 1;
          clone.position.z = (m % 20) * 3 - 50;
          clone.name = 'huojia';
          clone.traverse((child: any) => {
            child.layers.set(j + 1);
          });
          clone.layers.set(j + 1);
          this.scene.add(clone);

          // let index = Math.floor(Math.random() * 5);
          // let object = new THREE.Mesh(geometry, materials[index]);

          let object = new THREE.Mesh(
            geometry,
            new THREE.MeshBasicMaterial({ color: Math.random() * 0xffffff })
          );

          object.name = 'huowu';
          object.position.x = clone.position.x;
          object.position.y = clone.position.y + 0.4;
          object.position.z = clone.position.z;
          object.userData = {
            id: Math.random(),
            skuName: '花生豆'
          };
          object.layers.set(j + 1);
          this.scene.add(object);
        }
      }
    }
  }

  // 合并货架加入场景
  mergeAddToScene(gltf: any) {
    console.log(gltf.scene);
    var geometry = new THREE.BoxBufferGeometry(0.8, 0.8, 0.8);

    let materials = [
      new THREE.MeshLambertMaterial({ color: 0xff0000 }),
      new THREE.MeshLambertMaterial({ color: 0.2 * 0xff0000 }),
      new THREE.MeshLambertMaterial({ color: 0.4 * 0xff0000 }),
      new THREE.MeshLambertMaterial({ color: 0.6 * 0xff0000 }),
      new THREE.MeshLambertMaterial({ color: 0.8 * 0xff0000 })
    ];
    let mergeGeometry: Array<any> = [];
    let material = gltf.scene.children[0].material;
    gltf.scene.children[0].layers.set(6);
    let clone = gltf.scene.children[0].clone();

    let cloneGeo = new THREE.Geometry().fromBufferGeometry(clone.geometry);
    for (let m = 1; m <= 200; m++) {
      for (let i = 1; i <= 5; i++) {
        for (let j = 1; j <= 5; j++) {
          clone.position.x = i + (m / 20) * 7;
          clone.position.y = j;
          clone.position.z = (m % 20) * 3 - 50;
          clone.name = 'huojia';
          clone.traverse((child: any) => {
            child.layers.set(j);
          });

          let cloneGeo = new THREE.Geometry().fromBufferGeometry(
            clone.geometry
          );
          clone.updateMatrix();

          if (!mergeGeometry[j]) {
            mergeGeometry[j] = new THREE.Geometry();
          }
          mergeGeometry[j].merge(cloneGeo, clone.matrix, 0);

          // let index = Math.floor(Math.random() * 5);
          // let object = new THREE.Mesh(geometry, materials[index]);

          let object = new THREE.Mesh(
            geometry,
            new THREE.MeshLambertMaterial({ color: Math.random() * 0xffffff })
          );

          object.name = 'huowu';
          object.position.x = clone.position.x;
          object.position.y = clone.position.y + 0.4;
          object.position.z = clone.position.z;
          object.userData = {
            id: Math.random(),
            skuName: '花生豆'
          };
          object.layers.set(j);
          this.scene.add(object);
        }
      }
    }
    mergeGeometry.forEach((item: any, index) => {
      let mesh = new THREE.Mesh(item, material);
      mesh.layers.set(6);
      this.scene.add(mesh);
    });
  }

  // 初始化灯光
  initLight() {
    // 模拟远处类似太阳的光源
    var directionalLight = new THREE.DirectionalLight(0xffffff, 0.6);
    directionalLight.color.setHSL(0.1, 1, 0.95);
    directionalLight.position.set(0, 200, 0).normalize();
    this.scene.add(directionalLight);

    // AmbientLight,影响整个场景的光源
    var ambient = new THREE.AmbientLight(0xffffff, 1);
    ambient.position.set(0, 0, 0);
    this.scene.add(ambient);
  }

  // 初始化stats
  initStats() {
    this.stats = Stats();
    this.container.appendChild(this.stats.dom);
  }

  animate() {
    requestAnimationFrame(this.animate);
    this.update();
  }

  update() {
    this.stats.update();
    console.log(this.renderer.info.render.calls);
    this.renderer.render(this.scene, this.camera);
  }

  initRayCaster() {
    this.raycaster = new THREE.Raycaster();
    this.mouse = new THREE.Vector2();
  }

  onMouseClick(event: any) {
    event.preventDefault();
    // 通过鼠标点击位置,计算出 raycaster 所需点的位置,以屏幕为中心点,范围 -1 到 1;
    this.mouse.x =
      ((event.clientX - this.container.getBoundingClientRect().left) /
        this.container.offsetWidth) *
        2 -
      1;
    this.mouse.y =
      -(
        (event.clientY - this.container.getBoundingClientRect().top) /
        this.container.offsetHeight
      ) *
        2 +
      1;
    // 通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线位置
    this.raycaster.setFromCamera(this.mouse, this.camera);
    // 获取与射线相交的对象数组，其中的元素按照距离排序，越近的越靠前
    var intersects = this.raycaster.intersectObjects(this.scene.children, true);
    console.log(this.scene.children);
    console.log(intersects);
    if (
      intersects.length > 0 &&
      this.selectObject !== intersects[0].object &&
      intersects[0].object.name === 'huowu'
    ) {
      this.selectObject = intersects[0].object;
      this.selectObject.currentHex = this.selectObject.material.emissive.getHex();
      this.selectObject.material.emissive.setHex('0xff0000');
    }
  }

  initGui() {
    let layers = {
      一层: () => {
        this.camera.layers.toggle(1);
      },
      二层: () => {
        this.camera.layers.toggle(2);
      },
      三层: () => {
        this.camera.layers.toggle(3);
      },
      四层: () => {
        this.camera.layers.toggle(4);
      },
      五层: () => {
        this.camera.layers.toggle(5);
      },
      货架: () => {
        this.camera.layers.toggle(6);
      }
    };
    this.gui = new GUI();
    this.gui.add(layers, '一层');
    this.gui.add(layers, '二层');
    this.gui.add(layers, '三层');
    this.gui.add(layers, '四层');
    this.gui.add(layers, '五层');
    this.gui.add(layers, '货架');
  }
}
</script>
<style scoped lang='scss'>
#container {
  height: 600px;
  position: relative;
}
</style>
